package roborally.supporting;

import exception.IllegalConvertionException;
import be.kuleuven.cs.som.annotate.*;

/**
 * An enum representing different physical units.
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 2.0
 *
 */
@Value
public enum Unit {
	WATTSECOND("Ws") {
		
		/**
		 * Controls if this can convert to the given unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == null
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == this
		 * 		|	then result == true
		 * @return
		 * 		| result == unit.canConvertTo(this)
		 */
		@Override
		public boolean canConvertTo(Unit unit) {
			if(unit == null)
				return false;
			if(unit == this)
				return true;
			return unit.canConvertTo(this);
		}

		/**
		 * Returns the conversion factor needed to multiply this to convert to unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == this
		 * 		|	then result == 1D
		 * @return
		 * 		| result == 1 / unit.getConvertionFactor(this)
		 * @throws IllegalConvertionException
		 * 		When this can not converted to the unit.
		 * 		| !canConvertTo(unit)
		 */
		@Override
		public double getConvertionFactor(Unit unit)
				throws IllegalConvertionException {
			if(!this.canConvertTo(unit))
				throw new IllegalConvertionException("Can not convert the given units.", unit, this);
			if(unit == this)
				return 1D;
			return 1 / unit.getConvertionFactor(this);
		}
	}, GRAM("g") {
		/**
		 * Controls if this can convert to the given unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == null
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == this
		 * 		|	then result == true
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		| 	then result == false
		 * @return
		 * 		| result == unit.canConvertTo(this)
		 */
		@Override
		public boolean canConvertTo(Unit unit) {
			if(unit == null)
				return false;
			if(unit == this)
				return true;
			if(unit == Unit.WATTSECOND)
				return false;
			return unit.canConvertTo(this);
		}

		/**
		 * Returns the conversion factor needed to multiply this to convert to unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == this
		 * 		|	then result == 1D
		 * @return
		 * 		| result == 1 / unit.getConvertionFactor(this)
		 * @throws IllegalConvertionException
		 * 		When this can not converted to the unit.
		 * 		| !canConvertTo(unit)
		 */
		@Override
		public double getConvertionFactor(Unit unit)
				throws IllegalConvertionException {
			if(!this.canConvertTo(unit))
				throw new IllegalConvertionException("Can not convert the given units.", unit, this);
			if(unit == this)
				return 1D;
			return 1 / unit.getConvertionFactor(this);
		}
	}, JOULE("J") {
		/**
		 * Controls if this can convert to the given unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == null
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == this
		 * 		|	then result == true
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		| 	then result == true
		 * @return
		 * 		| if unit == Unit.GRAM
		 * 		| 	then result == false
		 * @return
		 * 		| result == unit.canConvertTo(this)
		 */
		@Override
		public boolean canConvertTo(Unit unit) {
			if(unit == null)
				return false;
			if(unit == this)
				return true;
			if(unit == Unit.WATTSECOND)
				return true;
			if(unit == Unit.GRAM)
				return false;
			return unit.canConvertTo(this);
		}

		/**
		 * Returns the conversion factor needed to multiply this to convert to unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == this
		 * 		|	then result == 1D
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		|	then result == 1D
		 * @return
		 * 		| result == 1 / unit.getConvertionFactor(this)
		 * @throws IllegalConvertionException
		 * 		If this can not converted to the unit.
		 * 		| !canConvertTo(unit)
		 */
		@Override
		public double getConvertionFactor(Unit unit)
				throws IllegalConvertionException {
			if(!this.canConvertTo(unit))
				throw new IllegalConvertionException("Can not convert the given units.", unit, this);
			if(unit == this)
				return 1D;
			if(unit == Unit.WATTSECOND)
				return 1D;
			return 1 / unit.getConvertionFactor(this);
		}
	}, KILOJOULE("kJ") {
		/**
		 * Controls if this can convert to the given unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == null
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == this
		 * 		|	then result == true
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		| 	then result == true
		 * @return
		 * 		| if unit == Unit.GRAM
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == Unit.JOULE
		 * 		| 	then result == true
		 * @return
		 * 		| result == unit.canConvertTo(this)
		 */
		@Override
		public boolean canConvertTo(Unit unit) {
			if(unit == null)
				return false;
			if(unit == this)
				return true;
			if(unit == Unit.WATTSECOND)
				return true;
			if(unit == Unit.GRAM)
				return false;
			if(unit == Unit.JOULE)
				return true;
			return unit.canConvertTo(this);

		}

		/**
		 *  Returns the conversion factor needed to make from an amount with this as unit, an amount with the given unit as unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == this
		 * 		|	then result == 1D
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		|	then result == 1000D
		 * @return
		 * 		| if unit == Unit.JOULE
		 * 		|	then result == 1000D
		 * @return
		 * 		| result == 1 / unit.getConvertionFactor(this)
		 * @throws IllegalConvertionException
		 * 		When this can not converted to the unit.
		 * 		| !canConvertTo(unit)
		 */
		@Override
		public double getConvertionFactor(Unit unit)
				throws IllegalConvertionException {
			if(!this.canConvertTo(unit))
				throw new IllegalConvertionException("Can not convert the given units.", unit, this);
			if(unit == this)
				return 1D;
			if(unit == Unit.WATTSECOND)
				return 1000D;
			if(unit == Unit.JOULE)
				return 1000D;
			return 1 / unit.getConvertionFactor(this);
		}
	}, KILOGRAM("kg") {

		/**
		 * Controls if this can convert to the given unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == null
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == this
		 * 		|	then result == true
		 * @return
		 * 		| if unit == Unit.WATTSECOND
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == Unit.GRAM
		 * 		| 	then result == true
		 * @return
		 * 		| if unit == Unit.JOULE
		 * 		| 	then result == false
		 * @return
		 * 		| if unit == Unit.KILOJOULE
		 * 		| 	then result == false
		 * @return
		 * 		| result == unit.canConvertTo(this)
		 */
		@Override
		public boolean canConvertTo(Unit unit) {
			if(unit == null)
				return false;
			if(unit == this)
				return true;
			if(unit == Unit.WATTSECOND)
				return false;
			if(unit == Unit.GRAM)
				return true;
			if(unit == Unit.JOULE)
				return false;
			if(unit == Unit.KILOJOULE)
				return false;
			return unit.canConvertTo(this);
		}

		/**
		 * Returns the conversion factor needed to make from an amount with this as unit, an amount with the given unit as unit.
		 * 
		 * @param unit The unit to convert to.
		 * @return
		 * 		| if unit == this
		 * 		|	then result == 1D
		 * @return
		 * 		| if unit == Unit.GRAM
		 * 		|	then result == 1000D
		 * @return
		 * 		| result == 1 / unit.getConvertionFactor(this)
		 * @throws IllegalConvertionException
		 * 		When this can not converted to the unit.
		 * 		| !canConvertTo(unit)
		 */
		@Override
		public double getConvertionFactor(Unit unit)
				throws IllegalConvertionException {
			if(!this.canConvertTo(unit))
				throw new IllegalConvertionException("Can not convert the given units.", unit, this);
			if(unit == this)
				return 1D;
			if(unit == Unit.GRAM)
				return 1000D;
			return 1 / unit.getConvertionFactor(this);		}
	};
	
	/**
	 * Creates a new Unit.
	 * 
	 * @param symbol The symbol for the new unit.
	 * @post The symbol for the new unit is the given symbol.
	 * 		| (new this).getSymbol() == symbol
	 */
	@Raw
	private Unit(String symbol){
		this.symbol = symbol;
	}
	
	/**
	 * Returns the symbol representing this unit.
	 */
	@Basic @Raw @Immutable
	public String getSymbol(){
		return symbol;
	}
	
	/**
	 * A variable storing the symbol of this unit.
	 */
	private final String symbol;
	
	/**
	 * Returns a textual representation of this unit.
	 * @return A textual representation of this unit.
	 * 		| result == this.getSymbol()
	 */
	@Override
	public String toString() {
		return this.getSymbol();
	}
	
	/**
	 * Controls if this can convert to the given unit.
	 * 
	 * @param unit The unit to convert to.
	 */
	public abstract boolean canConvertTo(Unit unit);
	
	/**
	 * Returns the conversion factor needed to multiply this to convert to unit.
	 * 
	 * @param unit The unit to convert to.
	 * @throws IllegalConvertionException
	 * 		When this can not converted to the unit.
	 * 		| !canConvertTo(unit)
	 */
	public abstract double getConvertionFactor(Unit unit) throws IllegalConvertionException;
}
